// Package check 实现了一个语义检查器
//
// 对于大多数使用者, [CheckTree] 是唯一一个使用的函数
//
// check不对代码编译的报错进行处理
//
// 如果要进行错误处理请自行使用 [pkg/gitee.com/u-language/u-language/ucom/errcode]
//
// # 检查的项目
//
//   - 变量的使用是否在声明之前
//   - 赋值语句的目的操作数与源操作数（参与操作不保存操作结果的数据）类型是否相等
//   - 赋值语句目的操作数是不是常量
//   - 运算表达式操作数的类型是否相等
//   - 运算表达式操作符是否能用于操作数的类型
//   - if,else if ,for 表达式是否表示布尔值
//   - for语句的3子句是否从左到右是变量声明语句，布尔表达式，赋值语句；3子句均可以为空
//   - 函数调用的传参与声明的参数是否匹配
//   - goto语句的标签是否在所处的函数中
//   - continue语句是否在for代码块内
//   - 变量或常量类型是否存在
//   - 自操作语句操作符是否能用于操作数的类型
//   - 自操作语句目的操作数是不是常量
//   - break语句是否在for代码块或switch代码块内
//   - switch表达式的类型是否和case表达式的类型是否相等
//   - 结构体是否是递归类型
//   - 是否循环导入
//   - 是否解引用非有类型指针
//
// # 辅助Ast
// [CheckTree] 进行语义检查的同时对抽象语法树进行下列辅助操作
//   - 记录运算表达式是否操作字符串
//   - 记录选择器中的最左值是否是结构体指针类型
//   - 如果选择器表示方法调用的标识符，记录这个方法所属的类型
//   - 记录在自动释放块内被调用的函数名
//   - 记录选择器是否是导入符号
//   - 记录选择器中的最左值是否是枚举类型
package check

import (
	"strings"

	"gitee.com/u-language/u-language/ucom/ast"
	"gitee.com/u-language/u-language/ucom/astdata"
	"gitee.com/u-language/u-language/ucom/enum"
	"gitee.com/u-language/u-language/ucom/errcode"
	"gitee.com/u-language/u-language/ucom/internal/utils"
)

// CheckASSIGN 检查赋值节点类型是否匹配
//   - ptr是被检查的赋值节点，不能为nil
//   - sbt是被检查的赋值节点所属代码块的符号表，不能为nil
//   - t是被检查节点所属的抽象语法树
//   - InAutoFree指示是否在自动释放块内
func CheckASSIGN(ptr *ast.ASSIGNNode, sbt *ast.Sbt, t *ast.Tree, InAutoFree bool) (code errcode.ErrCode, msg errcode.Msg) {
	var dest, src string //记录dest和src的类型
	defer check_recover(&code, &msg)
	code, msg, src = ret_type_str(ptr.Src, sbt, t, InAutoFree) //获取源操作数
	if code != errcode.NoErr {
		return code, msg
	}
	var info ast.SymbolInfo
	code, msg, info, _, dest = ret_info(ptr.Dest, sbt, t, InAutoFree)
	if code != errcode.NoErr {
		return code, msg
	}
	if info.Kind == enum.SymbolConst { //如果对常量赋值
		return errcode.ASSIGNErr, errcode.NewMsgAssignToConst(info.Name())
	}
	if !utils.Typ_is_equal(dest, src) { //如果dest和src的类型不匹配
		return errcode.TypeIsNotEqual, errcode.NewMsgAssignTypeIsNotEqual(dest, src)
	}
	return errcode.NoErr, nil
}

// checkOpExpr 检查运算表达式
//   - ptr是被检查的运算表达式，不能为nil
//   - sbt是被检查的运算表达式所属代码块的符号表，不能为nil
//   - t是被检查节点所属的抽象语法树
//   - InAutoFree指示是否在自动释放块内
func checkOpExpr(ptr *ast.OpExpr, sbt *ast.Sbt, t *ast.Tree, InAutoFree bool) (code errcode.ErrCode, msg errcode.Msg, typ string) {
	var src1, src2 string //记录dest和src的类型
	defer check_recover(&code, &msg)
	code, msg, src1 = ret_type_str(ptr.Src1, sbt, t, InAutoFree) //获取src1的类型
	if code != errcode.NoErr {                                   //有错误
		return code, msg, ""
	}
	code, msg, src2 = ret_type_str(ptr.Src2, sbt, t, InAutoFree) //获取src2的类型
	if code != errcode.NoErr {                                   //有错误
		return code, msg, ""
	}
	if !utils.Typ_is_equal(src1, src2) { //src1与src2类型不匹配
		return errcode.TypeIsNotEqual, errcode.NewMsgOpexprTypeIsNotEqual(src1, src2), ""
	}
	if utils.IsPtr(src1) && ptr.OP != enum.EqualOP && ptr.OP != enum.NoEqualOP { //如果是指针类型，且不是比较相等或不相等
		return errcode.PtrOpOnlyCmp, nil, ""
	}
	switch src1 {
	case enum.String: //如果操作数类型是字符串
		ptr.IsStr = true
	case enum.Bool: //如果操作数类型是布尔值
		switch ptr.OP {
		case enum.EqualOP, enum.NoEqualOP, enum.LogicAndOP, enum.LogicOrOP:
		default: //如果进行的运算不是比较相等或不相等
			return errcode.BoolOpOnlyCmpOrLogic, nil, enum.Bool
		}
	}
	switch ptr.OP {
	case enum.EqualOP, enum.NoEqualOP, enum.LessOP, enum.GreaterOP: //表达式表示bool值
		return errcode.NoErr, nil, enum.Bool
	}
	//src1与src2类型匹配
	return errcode.NoErr, nil, src1
}

// check_selector 检查选择器
func check_selector(ptr *ast.Objects, sbt *ast.Sbt) (code errcode.ErrCode, msg errcode.Msg, info ast.SymbolInfo, islocaldecl bool, funcinfo *ast.FuncInfo, isImport bool) {
	slen := len(ptr.Slice)
	info, islocaldecl = sbt.Have2(ptr.Slice[0].Name)
	old_right_typ, packageName := astdata.Typ(nil), ""
	switch info.Kind {
	case enum.SymbolVar:
		var_info := info.PtrVarInfoSbt()
		var typ_info ast.SymbolInfo
		g, _ := var_info.Type.(*ast.GenericInstantiation)
		typ_info = checkType(var_info.Type, sbt)
		if typ_info.Kind != enum.SymbolStruct {
			return errcode.NoOSelectorL, errcode.NewMsgSymbol(var_info.Name), ast.SymbolInfo{}, islocaldecl, nil, false
		}
		if var_info.Type.Typ()[0] == '&' {
			ptr.Slice[0].Kind |= ast.StructPtr
		}
		code, msg, funcinfo = check_selector_Left_right(ptr, typ_info.PtrStructDecl(), ptr.Slice[0].Name, ptr.Slice[1].Name, &old_right_typ, sbt, g)
		if code != errcode.NoErr {
			return code, msg, ast.SymbolInfo{}, islocaldecl, funcinfo, false
		}
	case enum.SymbolNoParameVar:
		var_info := info.Info.(*ast.VarNode)
		var typ_info ast.SymbolInfo
		g, _ := var_info.TYPE.(*ast.GenericInstantiation)
		typ_info = checkType(var_info.TYPE, sbt)
		if typ_info.Kind != enum.SymbolStruct {
			return errcode.NoOSelectorL, errcode.NewMsgSymbol(var_info.Name), ast.SymbolInfo{}, islocaldecl, nil, false
		}
		if var_info.TYPE.Typ()[0] == '&' {
			ptr.Slice[0].Kind |= ast.StructPtr
		}
		code, msg, funcinfo = check_selector_Left_right(ptr, typ_info.PtrStructDecl(), ptr.Slice[0].Name, ptr.Slice[1].Name, &old_right_typ, sbt, g)
		if code != errcode.NoErr {
			return code, msg, ast.SymbolInfo{}, islocaldecl, funcinfo, false
		}
	case enum.SymbolImport:
		if !IsExportSymbol(ptr.Slice[1].Name) {
			return errcode.NotAnExportSymbol, errcode.NewMsgSymbol(ptr.Slice[1].Name), ast.SymbolInfo{}, islocaldecl, nil, true
		}
		packageName = ptr.Slice[0].Name
		linfo := sbt.Have(utils.GeneratePackageSymbol(ptr.Slice[0].Name, ptr.Slice[1].Name))
		ptr.IsImportSymbol = true
		if linfo.Kind == enum.SymbolFunc {
			funcinfo = linfo.Info.(*ast.FuncInfo)
		}
		info = linfo
	case enum.SymbolEnum: //如果是枚举
		code, msg = matchEnum(ptr.Slice[0], info, ptr.Slice[1].Name, &old_right_typ, packageName)
		if code != errcode.NoErr {
			return code, msg, ast.SymbolInfo{}, islocaldecl, nil, packageName != ""
		}
	default:
		return errcode.NoOSelectorL, errcode.NewMsgSymbol(ptr.Slice[0].Name), ast.SymbolInfo{}, islocaldecl, nil, false
	}
	for i := 1; i < slen-1; i++ {
		if old_right_typ != nil {
			info = checkType(old_right_typ, sbt)
		}
		switch info.Kind {
		case enum.SymbolStruct:
			code, msg, funcinfo = check_selector_Left_right(ptr, info.PtrStructDecl(), ptr.Slice[i].Name, ptr.Slice[i+1].Name, &old_right_typ, sbt, nil)
			if code != errcode.NoErr {
				return code, msg, ast.SymbolInfo{}, islocaldecl, nil, packageName != ""
			}
		case enum.SymbolEnum:
			code, msg = matchEnum(ptr.Slice[i], info, ptr.Slice[i+1].Name, &old_right_typ, packageName)
			if code != errcode.NoErr {
				return code, msg, ast.SymbolInfo{}, islocaldecl, nil, packageName != ""
			}
		default:
			return errcode.NoOSelectorL, errcode.NewMsgSymbol(ptr.Slice[i-1].Name), ast.SymbolInfo{}, islocaldecl, nil, packageName != ""
		}
	}
	var typ string
	if old_right_typ != nil {
		typ = old_right_typ.Typ()
	} else {
		typ = info.Type()
	}
	if (int(ptr.Slice[0].Kind) & int(ast.LeaObj)) != 0 { //如果是取地址
		typ = "&" + typ
	}
	if (int(ptr.Slice[0].Kind)&int(ast.DerefObj)) != 0 && typ[0] == '&' { //如果需要处理的是解引用
		typ = typ[1:]
	}
	return errcode.NoErr, nil, ast.NewSymbolInfo_Var(ast.NewVarInfoSbt(ptr.Slice[len(ptr.Slice)-1].Name, ast.NewObject(ast.SymbolObj, typ), false, 0, "")), false, funcinfo, packageName != ""
}

// check_selector_Left_right 检查选择器左值是否包含右值
func check_selector_Left_right(ptr *ast.Objects, struct_info *ast.StructDecl, left string, right string, old_right_typ *astdata.Typ, sbt *ast.Sbt, g *ast.GenericInstantiation) (code errcode.ErrCode, msg errcode.Msg, funcinfo *ast.FuncInfo) {
	ok := false
	field := struct_info.FindField(right)
	if field.Name != "" {
		ok = true
		*old_right_typ = field.Type
	}
	if !ok { //如果选择器左值没有选择器右值
		var info ast.SymbolInfo
		if g != nil {
			info = sbt.Have(astdata.Generate_method_symbol(g.BaseName.Typ(), right))
		} else {
			info = sbt.HaveMethod(struct_info.Name, right)
		}

		if info.Kind == enum.NoSymbol {
			return errcode.SelectorLNoSelectorR, errcode.NewMsgSelectorLNoSelectorR(left, right), nil
		}
		funcinfo = info.Info.(*ast.MethodNode).FuncInfo
		//语法正确的方法声明至少有一个参数，如果不符合构建抽象语法树时就会报错
		if g != nil {
			ptr.T = g.Typ()
		} else {
			ptr.T = utils.Ret_no_lea(funcinfo.Parame[0].Type.Typ())
		}
		return errcode.NoErr, nil, funcinfo
	}
	return errcode.NoErr, nil, nil
}

// IsExportSymbol 返回一个符号是导出标识符
func IsExportSymbol(s string) bool {
	if s[0] == '_' { //如果开头是_
		return false
	}
	if s[0] >= 'a' && s[0] <= 'z' { //如果开头是小写字母
		return false
	}
	return true
}

// index 用于获取枚举类型是否有枚举值
//   - s是枚举类型有的所有枚举值
//   - v是被检查是否有的枚举值
func index(s []string, v string) int {
	for i := range s {
		if strings.HasSuffix(s[i], v) {
			return i
		}
	}
	return -1
}

// matchEnum 匹配枚举类型中是否有枚举值
//   - ptr是左值
//   - info是枚举类型的符号信息
//   - e是待匹配的枚举值
//   - PackageName是包名，本包为空
func matchEnum(ptr *ast.Object, info ast.SymbolInfo, e string, old_right_typ *astdata.Typ, PackageName string) (code errcode.ErrCode, msg errcode.Msg) {
	ptr.Kind |= ast.EnumObj
	enuminfo := info.Info.(*ast.EnumDecl)
	if PackageName == "" {
		*old_right_typ = ast.NewObject(ast.TypeObj, enuminfo.Name)
	} else {
		*old_right_typ = ast.NewObjects([]*ast.Object{ast.NewObject(ast.TypeObj, PackageName), ast.NewObject(ast.TypeObj, enuminfo.Name)})
	}
	if index(enuminfo.Enums, e) == -1 { //如果枚举类型中没有枚举值
		return errcode.SelectorLNoSelectorR, errcode.NewMsgSelectorLNoSelectorR(ptr.Name, utils.GeneratePackageSymbol(PackageName, e)) //这里生成带包名符号，是为了在生成报错信息时，不错误删除下划线前的内容
	}
	return errcode.NoErr, nil
}
